#ifndef LIBACCENT_ALGORITHMS_TAKE_WHILE_HPP
#define LIBACCENT_ALGORITHMS_TAKE_WHILE_HPP

#include "accent/core.hpp"
#include "accent/utility/safe_position.hpp"
#include "accent/utility/tag_functions.hpp"
#include <cassert>

namespace accent
{

  namespace detail
  {
    template <typename Range, typename Predicate>
    class taken_while
    {
      Predicate test;
      Range wrapped;
      bool finished;

      void check() {
        finished = wrapped.empty() || !test(wrapped.front());
      }

    public:
      using traversal = support::minimum_tag<typename Range::traversal,
                                             forward_traversal_tag>;

      taken_while(Predicate p, Range r) : test(p), wrapped(r) { check(); }

      bool empty() const { return finished; }
      void pop_front() {
        assert(!finished);
        wrapped.pop_front();
        check();
      }
      auto front() const -> decltype(wrapped.front()) {
        assert(!finished);
        return wrapped.front();
      }

      class position {
        friend class taken_while;

        using wrapped = support::safe_position<Range>;
        wrapped pos;
        bool finished;

      public:
        position() : pos(), finished(true) {}
        position(wrapped pos, bool finished) : pos(pos), finished(finished) {}

        explicit operator bool() const { return pos && !finished; }
        bool operator !() const { return !static_cast<bool>(*this); }
        auto operator *() const -> decltype(*pos) {
          assert(!finished);
          return *pos;
        }
        friend bool operator ==(const position& lhs, const position& rhs) {
          return lhs.pos == rhs.pos && lhs.finished == rhs.finished;
        }
      };

      position front_position() const {
        return { wrapped.front_position(), finished };
      }

      taken_while from(position p) const {
        assert(p && !p.finished);
        return { test, wrapped.from(p.pos) };
      }

      taken_while until(position p) const {
        assert(p && !p.finished);
        return { test, wrapped.until(p.pos) };
      }
    };
  }

  template <typename Range, typename Predicate>
  detail::taken_while<Range, Predicate> take_while(Predicate p, Range r)
  {
    return { p, r };
  }

}

#endif
